/*
 *      xmms2-notify.c
 *      
 *      Copyright 2006 Emmanuel Beffara <manu@beffara.org>
 *      Copyright 2009 Micah Bucy <micah.bucy@gmail.com>
 *      Copyright 2009 Thomas Frauendorfer <Thomas.Frauendorfer@googlemail.com>
 *      
 *      Thanks to systhread.net for the daemon code.  Great tutorial at 
 *      http://systhread.net/texts/200506cdaemon1.php
 *      
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *      
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *      
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *      MA 02110-1301, USA.
 *
 *      This file incorporates work (print_notify for GROWL) from growlnotify  
 *      covered by the following copyright and permission notice:  
 *  
 *      Copyright (c) The Growl Project, 2004-2005
 *      All rights reserved.
 *
 *      Redistribution and use in source and binary forms, with or without modification, 
 *      are permitted provided that the following conditions are met:
 *      1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *      2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *      3. Neither the name of Growl nor the names of its contributors
 *      may be used to endorse or promote products derived from this software
 *      without specific prior written permission.  
 *  
 *      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 *      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 *      TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 *      PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
 *      BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 *      DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 *      LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
 *      THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 *      NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 
 *      IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */ 
 
#ifdef __APPLE__
#ifdef __MACH__
#define GROWL
#endif
#endif

#ifdef __linux__
#define LIBNOTIFY
#endif

#ifdef __sun__
#define LIBNOTIFY
#endif

#ifdef __DragonFly__
#define LIBNOTIFY
#endif

#ifdef GROWL
#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>
#include "growl/GrowlDefines.h"
#include "growl/GrowlDefinesInternal.h"
#include "growl/GrowlPathway.h"
#import "growl/CFGrowlAdditions.h"
#endif

/* daemonize libraries */
#include <errno.h>
#include <stdlib.h>

#ifdef LIBNOTIFY
#include <libnotify/notify.h>
#include <librsvg/rsvg.h>
#endif

#include <unistd.h>
#include <getopt.h>
#include <stdio.h>
#include <string.h>
#include <xmmsclient/xmmsclient.h>
#include <xmmsclient/xmmsclient-glib.h>
#include <glib.h>
#include <glib/gi18n.h>

xmmsc_connection_t *xmms = NULL;
bool passprint = true;
bool print_once = false;
bool stopped = false;
bool do_daemonize = false;
bool custom_icon = false;
GString *custom_icon_path = NULL;
GString *text = NULL;
int status = 0;

/* Sanitize a string for HTML. {{{1 */

const struct trans {
	char c;
	const char *html;
} translation[] = {
	{ '<', "&lt;" },
	{ '>', "&gt;" },
	{ '&', "&amp;" },
	{ 0, NULL } };

static const char usage[] =
"Usage: xmms2-notify [options]\n\n"
"Options:\n"
"    -h,--help       Display this help\n"
"    -d,--daemonize  Daemonize xmms2-notify\n"
"    -n,--notify     Notify and then exit\n"
"    -i,--icon       Specify path to icon\n";

static void exit_notify(void *arg)
{
	GMainLoop *loop = arg;
	g_main_loop_quit(loop);
}

void append_html (GString *buf, const char *text)
{
	while (*text) {
		const struct trans *t = translation;
		while (t->c) {
			if (t->c == *text) {
				g_string_append(buf, t->html);
				goto next;
			}
			t++;
		}
		g_string_append_c(buf, *text);
next:
		text++;
	}
}

/* Print a notification {{{1 */
#ifdef GROWL
void print_notify(const char *summary, GString *text, int error, void *arg)
{
    if(passprint)
	{
		passprint = false;
		return;
	}
	int priority = 2;
	if(error) priority = 4;
	
	CFDataRef icon = NULL;
	if(custom_icon)
	{
		if(g_file_test(custom_icon_path->str, G_FILE_TEST_IS_REGULAR))
		{
			icon = (CFDataRef)readFile(custom_icon_path->str);
		}
	}
	else
	{
		if(g_file_test("/usr/share/pixmaps/xmms2-notify.icns", G_FILE_TEST_IS_REGULAR))
		{
			icon = (CFDataRef)readFile("/usr/share/pixmaps/xmms2-notify.icns");
		}
		if(g_file_test("/usr/local/share/pixmaps/xmms2-notify.icns", G_FILE_TEST_IS_REGULAR))
		{
			icon = (CFDataRef)readFile("/usr/local/share/pixmaps/xmms2-notify.icns");
		}
	}
	if (!icon) {
		puts(_("error loading icon file"));
		NSString *appPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:@"com.apple.Terminal"];
		if (appPath) {
			NSURL *appURL = [NSURL fileURLWithPath:appPath];
			if (appURL) {
				icon = (CFDataRef)copyIconDataForURL((NSURL *)appURL);
			}
		}
	}
	CFMutableStringRef title = CFStringCreateMutable(kCFAllocatorDefault, 0);
	CFStringAppend(title, CFSTR("xmms2 info..."));
	CFStringRef message = CFStringCreateWithCString(kCFAllocatorDefault, text->str, kCFStringEncodingUTF8);
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	CFStringRef applicationName = CFSTR("xmms2-notify");
	// Register with Growl
	CFStringRef name = CFSTR("xmms2 Growl Notification");
	CFArrayRef defaultAndAllNotifications = CFArrayCreate(kCFAllocatorDefault, (const void **)&name, 1, &kCFTypeArrayCallBacks);
	CFTypeRef registerKeys[4] = {
		GROWL_APP_NAME,
		GROWL_NOTIFICATIONS_ALL,
		GROWL_NOTIFICATIONS_DEFAULT,
		GROWL_APP_ICON
	};
	CFTypeRef registerValues[4] = {
		applicationName,
		defaultAndAllNotifications,
		defaultAndAllNotifications,
		icon
	};
	CFDictionaryRef registerInfo = CFDictionaryCreate(kCFAllocatorDefault, registerKeys, registerValues, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
	CFRelease(defaultAndAllNotifications);
	CFRelease(icon);

	// Notify
	CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
	CFStringRef clickContext = CFUUIDCreateString(kCFAllocatorDefault, uuid);
	CFNumberRef priorityNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &priority);
	CFBooleanRef stickyValue = kCFBooleanFalse;
	CFMutableDictionaryRef notificationInfo = CFDictionaryCreateMutable(kCFAllocatorDefault ,9, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_NAME, name);
	CFDictionarySetValue(notificationInfo, GROWL_APP_NAME, applicationName);
	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_TITLE, title);
	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_DESCRIPTION, message);
	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_PRIORITY, priorityNumber);
	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_STICKY, stickyValue);
	CFDictionarySetValue(notificationInfo, GROWL_NOTIFICATION_CLICK_CONTEXT, clickContext);
	CFRelease(priorityNumber);
	CFRelease(applicationName);
	CFRelease(title);
	CFRelease(message);
	CFRelease(clickContext);
	CFNotificationCenterRef distCenter = CFNotificationCenterGetDistributedCenter();
	NSConnection *connection = [NSConnection connectionWithRegisteredName:@"GrowlApplicationBridgePathway" host:nil];
	if (connection) {
		//Post to Growl via GrowlApplicationBridgePathway
		@try {
			NSDistantObject *theProxy = [connection rootProxy];
			[theProxy setProtocolForProxy:@protocol(GrowlNotificationProtocol)];
			id<GrowlNotificationProtocol> growlProxy = (id)theProxy;
			[growlProxy registerApplicationWithDictionary:(NSDictionary *)registerInfo];
			[growlProxy postNotificationWithDictionary:(NSDictionary *)notificationInfo];
		} @catch(NSException *e) {
			NSLog(@"exception while sending notification: %@", e);
		}
	} else {
		//Post to Growl via NSDistributedNotificationCenter
		NSLog(@"could not find local GrowlApplicationBridgePathway, falling back to NSDNC");
		CFNotificationCenterPostNotificationWithOptions(distCenter, (CFStringRef)GROWL_APP_REGISTRATION, NULL, registerInfo, kCFNotificationPostToAllSessions);
		CFNotificationCenterPostNotificationWithOptions(distCenter, (CFStringRef)GROWL_NOTIFICATION, NULL, notificationInfo, kCFNotificationPostToAllSessions);
	}
	while (CFRunLoopRunInMode(/* mode */ kCFRunLoopDefaultMode,
							  /* seconds; 0 means single iteration */ 0,
							  /* returnAfterSourceHandled */ TRUE) == kCFRunLoopRunHandledSource);
	CFRelease(registerInfo);
	CFRelease(notificationInfo);
	[pool release];
	if (print_once) exit_notify(arg);
	return;
}
/* system call method
void print_notify(const char *summary, GString *text, int error)
{
	GString *message = NULL;
	message = g_string_sized_new(256);
	g_string_assign(message, "growlnotify --message '");
	g_string_append(message, text->str);
	g_string_append(message, "'");
    if (error)
    {
        g_string_append(message, " --priority 2");
    }
    system(message->str);
}
*/
#endif
#ifdef LIBNOTIFY
void print_notify(const char *summary, GString *text, int error, void *arg)
{
    if(passprint)
	{
		passprint = false;
		return;
	}
	NotifyNotification *not;
	GError *err = NULL;
	GdkPixbuf *icon = NULL;

	if(custom_icon)
	{
		if(g_file_test(custom_icon_path->str, G_FILE_TEST_IS_REGULAR))
		{
			icon = rsvg_pixbuf_from_file_at_size(custom_icon_path->str, 48, 48, &err);
			err = NULL;
		}
	}
	else 
	{
		if(g_file_test("/usr/share/pixmaps/xmms2-notify.svg", G_FILE_TEST_IS_REGULAR))
		{
			icon = rsvg_pixbuf_from_file_at_size("/usr/share/pixmaps/xmms2-notify.svg", 48, 48, &err);
			err = NULL;
		}
		if(g_file_test("/usr/local/share/pixmaps/xmms2-notify.svg", G_FILE_TEST_IS_REGULAR))
		{
			icon = rsvg_pixbuf_from_file_at_size("/usr/local/share/pixmaps/xmms2-notify.svg", 48, 48, &err);
			err = NULL;
		}
	}

	not = notify_notification_new(summary, text->str, NULL, NULL);
	if(icon)
		notify_notification_set_icon_from_pixbuf(not, icon);
	else 
		puts(_("error: invalid svg file for icon"));
	if (error)
		notify_notification_set_urgency(not, NOTIFY_URGENCY_CRITICAL);
	notify_notification_set_timeout(not, 5000);
	if (!notify_notification_show(not, &err))
		fprintf(stderr, _("error: %s\n"), err->message);

	if (print_once) exit_notify(arg);
	return;
}
#endif

/* Format a Medialib entry {{{1 */
int cb_medialib_entry(xmmsv_t *val, void *arg)
{
	xmmsv_t *infodict;
	int32_t current;
	int32_t tracknr;
	const char *title;
	const char *album;
	const char *artist;
	
	g_string_truncate(text, 0);

	if (xmmsv_is_error(val)) {
		g_string_assign(text, xmmsv_get_error_old(val));
		print_notify(_("Xmms2 error"), text, 1, arg);
	}

	infodict = xmmsv_propdict_to_dict (val, NULL);
	if (!xmmsv_dict_entry_get_string(infodict, "title", &title)) {
		return 0;
	}
    xmmsv_dict_entry_get_string(infodict, "artist", &artist);
    xmmsv_dict_entry_get_string(infodict, "album", &album);
	g_string_assign(text, _("Artist: "));
	#ifdef LIBNOTIFY
	g_string_append(text, "<b>");
	#endif
	append_html(text, artist);
	#ifdef LIBNOTIFY
	g_string_append(text, "</b>");
	#endif
	g_string_append(text, "\n");
	g_string_append(text, _("Album: "));
	#ifdef LIBNOTIFY
	g_string_append(text, "<b>");
	#endif
	append_html(text, album);
	#ifdef LIBNOTIFY
	g_string_append(text, "</b>");
	#endif
	g_string_append(text, "\n");
	if(xmmsv_dict_entry_get_int(infodict, "tracknr", &tracknr))
		g_string_append_printf(text, "[%d] ", (int)tracknr);
	#ifdef LIBNOTIFY
	g_string_append(text, "<b>");
	#endif
	append_html(text, title);
	#ifdef LIBNOTIFY
	g_string_append(text, "</b>");
	#endif
	g_string_append(text, "\n");
	#ifdef LIBNOTIFY
	g_string_append(text, "<b>");
	#endif
	g_string_append(text, _("Status: "));
	#ifdef LIBNOTIFY
	g_string_append(text, "</b>");
	#endif
	switch(status)
	{
		case 1:
			g_string_append(text, _("Paused"));
			break;
		case 2:
			g_string_append(text, _("Stopped"));
			break;
		default:
			g_string_append(text, _("Playing"));
	}
	print_notify(_("xmms2 info..."), text, 0, arg);

	return 0;

}

/* Get the medialib entry for an id {{{1 */
int cb_current_id(xmmsv_t *val, void *arg)
{
	xmmsc_result_t *res;
	int32_t current;

	g_string_truncate(text, 0);

	if (xmmsv_is_error(val)) {
		g_string_assign(text, xmmsv_get_error_old(val));
		print_notify(_("Xmms2 error"), text, 1, arg);
		return 1;
	}

	xmmsv_get_int(val, &current);

	if (current == 0) {
		g_string_assign(text, _("Nothing loaded"));
		print_notify(_("xmms2 info..."), text, 0, arg);
		return 1;
	}

	res = xmmsc_medialib_get_info(xmms, current);
	xmmsc_result_notifier_set (res, cb_medialib_entry, arg);
	xmmsc_result_unref (res);

	return 1;
}

/* Update notification on status change */
int cb_status_change(xmmsv_t *val, void *arg)
{
	gint cb_status = 0;

	if (!xmmsv_is_error(val) && xmmsv_get_int(val, &cb_status)) {
		switch (cb_status) {
			case XMMS_PLAYBACK_STATUS_PAUSE:
				status = 1;
				break;
			case XMMS_PLAYBACK_STATUS_STOP:
				status = 2;
				stopped = true;
				break;
			default:
				if(stopped)
				{
					stopped = false;
					passprint = true;
				}
				status = 0;
		}
	}

	xmmsc_result_t *res;

	res = xmmsc_playback_current_id (xmms);
	xmmsc_result_notifier_set (res, cb_current_id, arg);
	xmmsc_result_unref (res);

	return 1;
}

/* Command line. {{{1 */
void parse_args(int argc, char **argv) 
{
	int c;
	while (1)
	{
		static struct option long_options[] =
		{
			/* These options set a flag.
			{"verbose", no_argument,       &verbose_flag, 1},
			{"brief",   no_argument,       &verbose_flag, 0}, */
			/* These options don't set a flag.
				We distinguish them by their indices. */
			{"daemonize",     no_argument,       0, 'd'},
			{"help",      no_argument,      0, 'h'},
			{"notify",      no_argument,      0, 'n'},
			{"icon",      required_argument,      0, 'i'},
			{0, 0, 0, 0}
		};
		/* getopt_long stores the option index here. */
		int option_index = 0;
		
		c = getopt_long (argc, argv, "dhni:",
			long_options, &option_index);
		
		/* Detect the end of the options. */
		if (c == -1)
			break;
		
		switch (c)
		{
			case 0:
				break;
			case 'd':
				do_daemonize = true;
				#ifdef GROWL
				puts(_("daemonize is not available on OS X due to incompatability with CoreFoundation\n"));
				printf("%s http://fork.sourceforge.net\n", _("As a workaround you can use fork: "));
				puts(_("If you use fork, however the terminal session will think it needs to stay open.\n"));
				puts(_("If you close the terminal session xmms2-notify will continue to run.\n"));
				#endif
				break;
			case 'h':
				puts(usage);
				exit(EXIT_SUCCESS);
				break;
			case 'n':
				print_once = true;
				break;
			case 'i':
				custom_icon = true;
				custom_icon_path = g_string_sized_new(0);
				g_string_append_printf(custom_icon_path, "%s", optarg);
				break;
			case '?':
				/* getopt_long already printed an error message. */
				break;
			default:
				return;
		}
	}
}

/* Daemonize xmms2-notify {{{1 */	
void daemonize() 
{
	pid_t   pid, sid;
    pid = fork();

    if (pid < 0) {
        exit(EXIT_FAILURE);
    } else if (pid > 0) {
        exit(EXIT_SUCCESS);
    }

    umask(0);

    sid = setsid();
    if (sid < 0) {
        exit(EXIT_FAILURE);
    }

    if ((chdir("/")) < 0) {
        exit(EXIT_FAILURE);
    }	
}

int main (int argc, char **argv)
{
	xmmsc_result_t *res;

	parse_args(argc, argv);

	text = g_string_sized_new(256);
	
	#ifdef LIBNOTIFY
	if(do_daemonize) daemonize();
	notify_init("xmms2-notify");
	#endif

	xmms = xmmsc_init("xmms2-notify");
	if (!xmmsc_connect(xmms, NULL)) {
		fprintf(stderr, _("connection failed: %s\n"), xmmsc_get_last_error(xmms));
		return 1;
	}

		GMainLoop *loop = g_main_loop_new(NULL, FALSE);
		res = xmmsc_playback_current_id (xmms);
		xmmsc_result_notifier_set (res, cb_current_id, loop);
		xmmsc_result_unref (res);

		res = xmmsc_broadcast_playback_current_id (xmms);
		xmmsc_result_notifier_set (res, cb_current_id, loop);
		xmmsc_result_unref (res);
		
		res = xmmsc_playback_status (xmms);
		xmmsc_result_notifier_set (res, cb_status_change, loop);
		xmmsc_result_unref (res);
		
		res = xmmsc_broadcast_playback_status (xmms);
		xmmsc_result_notifier_set (res, cb_status_change, loop);
		xmmsc_result_unref (res);
		
		xmmsc_disconnect_callback_set(xmms, (xmmsc_disconnect_func_t) exit_notify, loop);	

		xmmsc_mainloop_gmain_init(xmms);
		g_main_loop_run(loop);

	return 0;
	
}
